/*
 * Copyright (C) 2020 FoilPlanet
 *
 */

#include "anbox/codec/server.h"
#include "anbox/codec/audio_sink.h"
#include "anbox/codec/video_sink.h"

#include "anbox/network/published_socket_connector.h"
#include "anbox/network/delegate_connection_creator.h"
#include "anbox/network/local_socket_messenger.h"
#include "anbox/network/message_processor.h"
#include "anbox/common/type_traits.h"
#include "anbox/system_configuration.h"
#include "anbox/utils.h"
#include "anbox/logger.h"

using namespace std::placeholders;
using namespace anbox;
using namespace anbox::codec;

/**
 * AudioForwarder
 */
class AudioForwarder : public anbox::network::MessageProcessor {
 public:
  AudioForwarder(const std::shared_ptr<Runtime> &runtime) : sink_(runtime)
  {
    // NOTHING
  }

  bool process_data(const std::vector<std::uint8_t> &data) override
  {
    sink_.write_data(data);
    return true;
  }

 private:
  AudioSink sink_;
};

/**
 * VideoForwarder
 */
class VideoForwarder : public anbox::network::MessageProcessor {
 public:
  VideoForwarder(const std::shared_ptr<Runtime> &runtime) : sink_(runtime)
  {
    // NOTHING
  }

  bool process_data(const std::vector<std::uint8_t> &data) override
  {
    sink_.process_data(&data.at(0), data.size());
    return true;
  }

 private:
  VideoSink sink_;
};

/**
 * anbox:codec:Server
 */
Server::Server(const std::shared_ptr<Runtime>& rt, const std::shared_ptr<platform::BasePlatform> &platform)
  : platform_(platform),
    runtime_(rt),
    socket_file_(utils::string_format("%s/anbox_audio", SystemConfiguration::instance().socket_dir())),
    connector_(std::make_shared<network::PublishedSocketConnector>(
               socket_file_, rt,
               std::make_shared<network::DelegateConnectionCreator<boost::asio::local::stream_protocol>>(std::bind(&Server::create_connection_for, this, _1)))),
    connections_(std::make_shared<network::Connections<network::SocketConnection>>()),
    next_id_(0) 
{
  // FIXME: currently creating the socket creates it with the rights of
  // the user we're running as. As this one is mapped into the container
  ::chmod(socket_file_.c_str(), 0777);

  // should init mpp
  video::Sink::pre_init(runtime_);
}

Server::~Server()
{
  // NOTHING
}

void Server::create_connection_for(std::shared_ptr<boost::asio::basic_stream_socket<boost::asio::local::stream_protocol>> const& socket) {
  auto const messenger =
      std::make_shared<network::LocalSocketMessenger>(socket);

  // We have to read the client flags first before we can continue
  // processing the actual commands
  ClientInfo client_info;
  auto err = messenger->receive_msg(
      boost::asio::buffer(&client_info, sizeof(ClientInfo)));
  if (err) {
    ERROR("Failed to read client info: %s", err.message());
    return;
  }

  std::shared_ptr<network::MessageProcessor> processor;

  INFO("Got client with type %d (size %d)", static_cast<int>(client_info.type), sizeof(client_info));

  switch (client_info.type) {
  case ClientInfo::Type::Playback:
    processor = std::make_shared<AudioForwarder>(runtime_);
    break;

  case ClientInfo::Type::Recording:
    // xiaofeng: TODO
    break;

  case ClientInfo::Type::ScreenShot:
    processor = std::make_shared<VideoForwarder>(runtime_);
    break;

  case ClientInfo::Type::VideoDecoder:
  case ClientInfo::Type::VideoEncoder:
    // xiaofeng: TODO
    // processor = std::make_shared<VideoCodec>(client_info.type == ClientInfo::Type::VideoEncoder);
    break;

  default:
    ERROR("Invalid client type %d", static_cast<int>(client_info.type));
    return;
  } // switch(client_info)

  // Everything ok, so approve the client by sending the requesting client
  // info back. Once we have more things to negotiate we will send a modified
  // client info struct back.
  messenger->send(reinterpret_cast<char*>(&client_info), sizeof(client_info));

  auto connection = std::make_shared<network::SocketConnection>(
        messenger, messenger, next_id(), connections_, processor);
  connections_->add(connection);

  connection->read_next_message();
}

int Server::next_id() {
  return next_id_.fetch_add(1);
}
